package cn.huaqingcheng.tool.http;

import cn.huaqingcheng.tool.core.CollTool;
import cn.huaqingcheng.tool.core.StrTool;
import cn.huaqingcheng.tool.core.StringPool;
import cn.huaqingcheng.tool.core.function.Function;
import lombok.NoArgsConstructor;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiConsumer;

/**
 * Http Query 参数包装
 * TODO: 序列化相关 暂未实现
 *
 * @apiNote 期望提供一种简单易用的命令式API; 区分大小写 <br>
 * 所有 {@code addXXX} 方法为写入, 如果存在则覆盖 <br>
 * 所有 {@code putXXX} 方法为追加, 不会覆盖已存在值, 但是不保证同名属性查询时的顺序 <br>
 * 所有 {@code get*} 方法为查询一个, 如果存在同名属性 选第一个, 但是不保证稳定顺序 <br>
 * 所有 {@code getAll} 方法 查询指定属性的所有值 <br>
 * {@link #addIfNotExist(String, Object)}  {@link #forEach(BiConsumer)} {@link #isEmpty()} 等是便捷的工具
 * @implNote 实现是非线程安全的; 应该不可能有多线程共同构建一个请求参数的必要...
 */
@SuppressWarnings({"SpellCheckingInspection", "ClassNamePrefixedWithPackageName", "unused", "UnusedReturnValue"})
@NoArgsConstructor
public class HttpQuerys {

    /**
     * 重复项列表的默认大小
     */
    public static final int CAPACITY = 2;

    /**
     * 存储的值映射
     */
    @NotNull
    private final Map<String, List<String>> value = new HashMap<>(16, 1);

    /**
     * 批量添加
     *
     * @param map 属性映射
     */
    public HttpQuerys addAll(@Nullable Map<String, String> map) {
        if (CollTool.isNotEmpty(map)) {
            map.forEach(this::add);
        }
        return this;
    }

    /**
     * 添加属性, 如果已存在 则覆盖
     *
     * @param k 属性 key 区分大小写
     * @param v 属性值 {@link Object#toString()}
     * @see #put(String, Object)
     */
    public HttpQuerys add(@Nullable String k, @Nullable Object v) {
        if (StrTool.isEmpty(k)) {
            return this;
        }
        ArrayList<String> list = new ArrayList<>(CAPACITY);
        list.add(Objects.requireNonNullElse(v, StringPool.EMPTY).toString());
        value.put(k, list);

        return this;
    }

    /**
     * 合并, 如果属性名重复 以参数优先合并
     */
    public HttpQuerys addAll(@Nullable HttpQuerys querys) {
        if (querys != null) {
            querys.value.forEach(this::add);
        }
        return this;
    }

    /**
     * 合并, 如果属性名重复 以参数优先合并
     */
    public HttpQuerys addAll(@Nullable String queryStr) {
        queryStr(queryStr, this::add);
        return this;
    }

    /**
     * 消费诸如: {@code a=x&b=xx} 形式的字符串
     *
     * @param queryStr {@code a=x&b=xx} 形式的字符串
     * @param consumer 消费者函数
     * @implNote 实现依赖 {@link StrTool#AND} {@link StrTool#EQ} 字符串拆分 <br>
     * 如果键值对值不存在, 传入 空字符串; <br>
     * 如果存在多个 {@link StrTool#EQ} 比如: a=x=xx=xxx 忽略多余的部分
     */
    private void queryStr(@Nullable String queryStr, @NotNull BiConsumer<String, String> consumer) {
        if (StrTool.isEmpty(queryStr)) {
            return;
        }
        for (String i : queryStr.split(StringPool.AND)) {
            String[] item = i.split(StringPool.EQ);
            if (item.length == 0) {
                continue;
            }
            if (item.length == 1) {
                consumer.accept(item[0], StringPool.EMPTY);
            } else {
                consumer.accept(item[0], item[1]);
            }
        }
    }

    /**
     * 批量添加
     *
     * @param map 属性映射
     */
    public HttpQuerys putAll(@Nullable Map<String, String> map) {
        if (CollTool.isNotEmpty(map)) {
            map.forEach(this::put);
        }
        return this;
    }

    /**
     * 添加属性, 如果属性名重复, 以参数优先合并
     *
     * @see #add(String, Object)
     */
    public HttpQuerys put(@Nullable String k, @Nullable Object v) {
        if (StrTool.isEmpty(k)) {
            return this;
        }
        if (!value.containsKey(k)) {
            value.put(k, new ArrayList<>(CAPACITY));
        }
        value.get(k).add(Objects.requireNonNullElse(v, StringPool.EMPTY).toString());

        return this;
    }

    /**
     * 合并, 如果属性名重复, 累加
     */
    public HttpQuerys putAll(@Nullable HttpQuerys querys) {
        if (querys != null) {
            querys.value.forEach(this::put);
        }
        return this;
    }

    /**
     * 合并, 如果属性名重复, 累加
     */
    @SuppressWarnings("UnusedReturnValue")
    public HttpQuerys putAll(@Nullable String queryStr) {
        queryStr(queryStr, this::put);
        return this;
    }

    /**
     * 如果不存在, 则添加
     *
     * @param k 属性 key 区分大小写
     * @param v 属性值 {@link Object#toString()}
     */
    public HttpQuerys addIfNotExist(@Nullable String k, @Nullable Object v) {
        if (StrTool.isEmpty(k) || value.containsKey(k)) {
            return this;
        }
        return add(k, v);
    }

    /**
     * 如果不存在,  执行给定函数添加
     *
     * @param k 属性 key 区分大小写
     */
    public HttpQuerys addIfNotExist(String k, Function<String, Object, Exception> fun) {
        if (StrTool.isEmpty(k) || value.containsKey(k)) {
            return this;
        }
        try {
            return add(k, fun.apply(k));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取指定的值
     *
     * @see #get(String)
     */
    @Contract(pure = true)
    public Optional<String> getOpt(@Nullable String k) {
        return Optional.ofNullable(get(k));
    }

    /**
     * 查询
     *
     * @param k 属性 key
     * @return 如果指定的 key 仅和一个值关联, 返回这个值,
     * 如果和多个值关联 返回"第一个" 不保证顺序稳定性
     * 如果传入key 为空, 返回 null
     */
    @Nullable
    @Contract(value = "null -> null", pure = true)
    public String get(@Nullable String k) {
        if (StrTool.isEmpty(k) || !value.containsKey(k)) {
            return null;
        }
        List<String> list = value.get(k);
        if (CollTool.isNotEmpty(list)) {
            return list.get(0);
        }

        return null;
    }

    /**
     * 查询所有
     *
     * @see #getAll()
     * @see #toString()
     */
    @Contract(pure = true)
    public List<String> getAll(String k) {
        if (StrTool.isEmpty(k) || !value.containsKey(k)) {
            return List.of();
        }
        return Collections.unmodifiableList(value.get(k));
    }

    /**
     * 查询所有
     *
     * @see #getAll(String)
     * @see #toString()
     */
    @Contract(pure = true)
    public List<Map.Entry<String, String>> getAll() {
        return value.entrySet()
            .stream()
            .flatMap(e -> {
                String key = e.getKey();
                return e.getValue()
                    .stream()
                    .map(i -> (Map.Entry<String, String>) new AbstractMap.SimpleEntry<>(key, i));
            })
            .toList();
    }

    public boolean isEmpty() {
        return value.isEmpty();
    }

    public boolean isNotEmpty() {
        return !value.isEmpty();
    }

    @Override
    public String toString() {
        if (value.isEmpty()) {
            return StringPool.EMPTY;
        }
        StringBuilder sb = new StringBuilder(value.size() * 5);
        forEach((k, v) -> sb.append(k).append(StringPool.EQ).append(v).append(StringPool.AND));
        sb.deleteCharAt(sb.length() - 1);
        sb.trimToSize();

        return sb.toString();
    }

    /**
     * 遍历所有属性
     */
    public void forEach(BiConsumer<String, String> consumer) {
        value.forEach((k, l) -> l.forEach(v -> consumer.accept(k, v)));
    }

}
